/* + C O P Y R I G H T * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* See top source directory for COPYRIGHT.txt                              */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/* - C O P Y R I G H T * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "athd.h"

/* cmds */
static athd_cmd_act _athd_cmd_help(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_echo(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_quit(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_exit(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_stat(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_shutdown(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_reset(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_show(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_set(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_engine(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_job(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_phys(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_farm(athd_cmd_req *req, char *cursor);
static athd_cmd_act _athd_cmd_context(athd_cmd_req *req, char *cursor);

/* supporting cmd */
static athd_cmd_act _athd_cmd_show_all_engines(athd_cmd_req *req,char *cursor);
static athd_cmd_act _athd_cmd_show_all_jobs(athd_cmd_req *req, char *cursor);
static apr_status_t _athd_cmd_update_phys_map(athd_cmd_req *req,
					      athd_engine *engine);
static apr_status_t _athd_cmd_update_farm_map(athd_cmd_req *req,
					      athd_engine *engine);

/* utils */
static const athd_cmd * _athd_cmd_get(const char *str);
static void _athd_cmd_ctx_enter(athd_cmd_handler *handler, const athd_cmd *cmd);
static void _athd_cmd_ctx_exit(athd_cmd_handler *handler);
static athd_cmd_ctx * _athd_cmd_ctx_alloc(apr_pool_t *pool);

#define CLIENT(s) athd_cmd_io_send_add(req, s)

typedef athd_cmd_act (*athd_cmd_func) (athd_cmd_req *r, char *str);

struct athd_cmd {
    const char *name;
    athd_cmd_func func;
    const char *help;
};

enum athd_cmd_ctx_type {
    ATHD_CMD_NULL,
    ATHD_CMD_SHOW,
    ATHD_CMD_ENGINE,
    ATHD_CMD_JOB,
    ATHD_CMD_FARM,
    ATHD_CMD_PHYS
};

struct athd_cmd_ctx {
    apr_pool_t *pool;
    athd_cmd_ctx *prev;
    const athd_cmd *cmd;
    char *prompt;
    void *data;
    enum athd_cmd_ctx_type type;
};

#define _ATHD_CMD(c, f, s) { c, f, s }

static const athd_cmd _athd_cmd_map[] = 
  {
      _ATHD_CMD("help", _athd_cmd_help, "Prints help."),
      _ATHD_CMD("echo", _athd_cmd_echo, "Echos input."),
      _ATHD_CMD("exit", _athd_cmd_exit, "Exit the current command context."),
      _ATHD_CMD("..", _athd_cmd_exit, "Alias for \"exit\"."),
      _ATHD_CMD("quit", _athd_cmd_quit, "Quit the interactive session."),
      _ATHD_CMD("config", _athd_cmd_stat, "Produce local config info."),
      _ATHD_CMD("shutdown", _athd_cmd_shutdown, "Complete shutdown."),
      _ATHD_CMD("reset", _athd_cmd_reset, "Reset jobs and engine connections."),
      _ATHD_CMD("show", _athd_cmd_show, "Show info on a context."),
      _ATHD_CMD("set", _athd_cmd_set, "Set info on job or engine."),
      _ATHD_CMD("engine", _athd_cmd_engine, "Enter engine interaction mode."),
      _ATHD_CMD("job", _athd_cmd_job, "Enter job interaction mode."),
      _ATHD_CMD("farm", _athd_cmd_farm, "Interact with engine farm."),
      _ATHD_CMD("phys", _athd_cmd_phys, "Interact with engine physical."),
      _ATHD_CMD("context", _athd_cmd_context, "Show complete context."),
      {NULL}
  };

athd_cmd_act
athd_cmd_parser(athd_cmd_req *req, char *cursor)
{
    int rv = ATHD_ACT_CONTINUE;
    ASSERT(cursor);
    if (cursor)
	cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
    if ((cursor) && (strlen(cursor) > 0))
      {
#define ATHD_CMD_SIMPLE(s, n)
	  if (ATHD_CMD_IS_QUIT(*cursor))
	      rv = ATHD_ACT_STOP;
	  else if (!strncasecmp(cursor, ATHD_CMD_STR_STREAM, 8))
	    {
		req->handler->type = ATHD_CMD_STREAM;
		rv = ATHD_ACT_CONTINUE;
	    }
	  else if (!strncasecmp(cursor, ATHD_CMD_STR_SHELL, 7))
	    {
		req->handler->type = ATHD_CMD_SHELL;
		/* get a prompt out there on this pass */
		athd_cmd_io_send_flush(req);
		rv = ATHD_ACT_CONTINUE;
	    }
	  else if (!strncasecmp(cursor, CRLF, 2))
	    {
	      rv = ATHD_ACT_CONTINUE;
	      athd_cmd_io_send_flush(req);
	    }
	  else
	    {
		const athd_cmd *cmd = _athd_cmd_get(cursor);
		if (cmd)
		  {
		      athd_cmd_ctx *cmd_ctx;
		      /* ctx exit is managed inside each func */
		      if ((req->handler->cmd_ctx)
			  && (req->handler->cmd_ctx->cmd == cmd))
			{
			    char *msg = 
			      apr_psprintf(req->pool, ATHD_ERR_CTXAGAIN,
					   cmd->name);
			    CLIENT(msg);
			}
		      else
			{
			    _athd_cmd_ctx_enter(req->handler, cmd);
			    cursor += strlen(cmd->name);
			    ath_chompn(cursor, ATHD_MAX_CMD_RECV);
			    ath_chompn(cursor, ATHD_MAX_CMD_RECV);
			    rv = cmd->func(req, cursor);
			}
		  }
		else
		  {
		      CLIENT(ATHD_ERR_NOCMD);
		      athd_cmd_io_send_flush(req);
		  }
	    }
      }

    return rv;
}

/* create prompt */
apr_status_t
athd_cmd_parser_prompter(athd_cmd_req *req)
{
    athd_cmd_ctx *cmd_ctx;
    cmd_ctx = req->handler->cmd_ctx;
    req->handler->prompt = NULL;
    while (cmd_ctx)
      {
	  if (cmd_ctx->prompt)
	      req->handler->prompt = 
		apr_pstrcat(req->pool, 
			    cmd_ctx->prompt,  req->handler->prompt, NULL);
	  if (cmd_ctx->prev)
	      cmd_ctx = cmd_ctx->prev;
	  else 
	      cmd_ctx = NULL;
      }
    req->handler->prompt = 
      apr_pstrcat(req->pool, ATHD_CMD_PROMPT_BASE, req->handler->prompt);
}

static athd_cmd_act
_athd_cmd_help(athd_cmd_req *req, char *cursor)
{
    const athd_cmd *cmd = NULL;
    if (cursor)
	cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
    if (cursor)
	cmd = _athd_cmd_get(cursor);
    if (cmd)
      {
	  ASSERT(cmd->help);
	  CLIENT(cmd->help);
      }
    else 
      {
	  const athd_cmd *cmd;
	  int i;
	  for (i = 0; (_athd_cmd_map[i].name != NULL); i++)
	    {
		char *strout;
		cmd = &_athd_cmd_map[i];
		ASSERT(cmd->name);
		ASSERT(cmd->help);
		strout = apr_pstrcat(req->pool, cmd->name, "\t: ", 
				     cmd->help, "\n", NULL);
		CLIENT(strout);
	    }
      }
    athd_cmd_io_send_flush(req);
    _athd_cmd_ctx_exit(req->handler);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_echo(athd_cmd_req *req, char *cursor)
{
    if (cursor)
	cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
    if (cursor)
	athd_cmd_io_send_add(req, cursor);
    athd_cmd_io_send_flush(req);
    _athd_cmd_ctx_exit(req->handler);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_quit(athd_cmd_req *req, char *cursor)
{
    if (req->handler->type == ATHD_CMD_SHELL)
      {
	  CLIENT("Goodbye.\n");
	  athd_cmd_io_send_flush(req);
      }
    _athd_cmd_ctx_exit(req->handler);
    return ATHD_ACT_STOP;
}

static athd_cmd_act
_athd_cmd_exit(athd_cmd_req *req, char *cursor)
{
    /* exit the exit context first :) */
    _athd_cmd_ctx_exit(req->handler);
    if (!req->handler->cmd_ctx)
	CLIENT(ATHD_ERR_NOCTX);
    else
	_athd_cmd_ctx_exit(req->handler);
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_stat(athd_cmd_req *req, char *cursor)
{
    char *strout;
    athd_conf *conf = athd_conf_current_get();
    ath_node *listctx;
    char *engines = NULL;
    char *jobs = NULL;
    ath_list_rewind_r(conf->engines, &listctx);
    while (ath_list_hasnext_r(conf->engines, &listctx))
      {
	  athd_engine *eng = (athd_engine *)
	    ath_list_getnext_r(conf->engines, &listctx);
	  if (!engines)
	      engines = apr_pstrcat(req->pool, eng->name, NULL);
	  else
	      engines = apr_pstrcat(req->pool, engines, ", ", 
				    eng->name, NULL);
      }
    ath_list_rewind_r(conf->jobs, &listctx);
    while (ath_list_hasnext_r(conf->jobs, &listctx))
      {
	  athd_job *job = (athd_job *)
	    ath_list_getnext_r(conf->jobs, &listctx);
	  if (!jobs)
	      jobs = apr_pstrcat(req->pool, job->name, NULL);
	  else
	      jobs = apr_pstrcat(req->pool, jobs, ", ", 
				    job->name, NULL);
      }
    strout = apr_psprintf(req->pool, 
			  "-- Config -------------------------\n"
			  "Server name: %s\n"
			  "Listen port: %d\n"
			  "Log mutex path: %s\n"
			  "Pid file: %s\n"
			  "Log file: %s\n"
			  "Ip lb enabled: %s\n"
			  "Stats pulse: %d/sec\n"
			  "Engines: %s\n"
			  "Jobs: %s\n"
			  "-- State --------------------------\n"
			  "Host: %s p(g)id: %d\n"
			  "Current log level: %s\n"
			  "Current cmd session: %ld\n"
			  "Active cmd sessions: %d\n" 
			  "Total cmd sessions: %d\n"
			  "Total requests: %ld\n",
			  /* Config */
			  conf->server_name,
			  conf->port,
			  conf->log_lock_path,
			  conf->pidfile,
			  conf->logfile,
			  conf->ip_lb ? "yes" : "no",
			  conf->stats_pulse,
			  engines ? engines : "(none)",
			  jobs ? jobs : "(none)",
			  /* State */
			  conf->server_name,
			  GET_PID(),
			  athd_log_get_strlvl(),
			  req->handler->id,
			  athd_cmd_handler_sessions_active(),
			  athd_cmd_handler_sessions_total(),
			  athd_cmd_handler_requests_total());
    CLIENT(strout);
    athd_cmd_io_send_flush(req);
    _athd_cmd_ctx_exit(req->handler);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_shutdown(athd_cmd_req *req, char *cursor)
{
    if (req->handler->type == ATHD_CMD_SHELL)
      {
	  CLIENT("Sending shutdown signal.\n");
	  athd_cmd_io_send_flush(req);
      }
    athd_log(ATHD_CRIT, "Shutdown via cmd session request.");
    _athd_cmd_ctx_exit(req->handler);
    KILL_PID(GET_PID(), SIGTERM);
    return ATHD_ACT_STOP;
}

static athd_cmd_act
_athd_cmd_reset(athd_cmd_req *req, char *cursor)
{
    CLIENT("Sending reset signal.\n");
    athd_cmd_io_send_flush(req);
    athd_log(ATHD_CRIT, "Reset via cmd session request.");
    _athd_cmd_ctx_exit(req->handler);
    KILL_PID(GET_PID(), SIGHUP);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act 
_athd_cmd_show(athd_cmd_req *req, char *cursor)
{
    char *showwhat;
    char *sep = " ";
    char *inter;
    athd_conf *conf = athd_conf_current_get();
    if (cursor)
	cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
    ath_chompn(cursor, ATHD_MAX_CMD_RECV);

    if (req->handler->cmd_ctx->prev)
      {
	  char *strout = NULL;
#define CMD_MATCH(t) (req->handler->cmd_ctx->prev->type == t)
	  if CMD_MATCH(ATHD_CMD_ENGINE)
	  {
	      athd_engine *engine = (athd_engine *)
		req->handler->cmd_ctx->prev->data;
	      ASSERT(engine);
	      strout = athd_engine_to_str(req->pool, engine);
	  }
	  else if CMD_MATCH(ATHD_CMD_JOB)
	  {
	      athd_job *job = (athd_job *)
		req->handler->cmd_ctx->prev->data;
	      ASSERT(job);
	      strout = athd_job_to_str(req->pool, job);
	  }
	  else if CMD_MATCH(ATHD_CMD_FARM)
	  {
	      if (req->handler->type == ATHD_CMD_SHELL)
		{
		    ath_farm *farm = (ath_farm *)
		      req->handler->cmd_ctx->prev->data;
		    ASSERT(farm);
		    strout = 
		      apr_psprintf(req->pool,
				   "[%d] %s (online: %s) \n",
				   farm->idx, farm->name,
				   farm->on ? "yes" : "no");
		}
	      else
		{
		    char *serial = (char *)req->handler->cmd_ctx->prev->data;
		    ASSERT(serial);
		    strout = apr_pstrcat(req->pool, serial, "\n");
		}
	  }
	  else
	      strout = ATHD_ERR_NOTIMPL;
	  CLIENT(strout);
	  _athd_cmd_ctx_exit(req->handler);
      }
    else if ((cursor) && (strlen(cursor) > 0))
      {
	  DEBUGOUT("arg: %s", cursor);
	  _athd_cmd_ctx_exit(req->handler); /* exit current show */
	  athd_cmd_parser(req, cursor); 
	  _athd_cmd_ctx_enter(req->handler, _athd_cmd_get("show"));
	  _athd_cmd_show(req, NULL); /* run show */
      }
    else
      {
	  _athd_cmd_show_all_engines(req, cursor);
	  _athd_cmd_show_all_jobs(req, cursor);
	  _athd_cmd_ctx_exit(req->handler);
      }
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act 
_athd_cmd_set(athd_cmd_req *req, char *cursor)
{
    CLIENT(ATHD_ERR_NOTIMPL);
    _athd_cmd_ctx_exit(req->handler);
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act 
_athd_cmd_engine(athd_cmd_req *req, char *cursor)
{
    athd_engine *engine = NULL;
    athd_conf *conf = athd_conf_current_get();
    if (cursor)
	cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
    ath_chompn(cursor, ATHD_MAX_CMD_RECV);
    if ((cursor) && (strlen(cursor) > 0))
      {
	  engine = athd_engine_for_name(conf->engines, cursor);
	  if (engine)
	    {
		req->handler->cmd_ctx->prompt =  
		  apr_psprintf(req->handler->cmd_ctx->pool, 
			       "(%s) ", engine->name);
		req->handler->cmd_ctx->type = ATHD_CMD_ENGINE;
		req->handler->cmd_ctx->data = (void *)engine;
	    }
	  else
	      CLIENT(ATHD_ERR_NOENG);
      }
    else 
	_athd_cmd_show_all_engines(req, cursor);
    if (!engine)
	_athd_cmd_ctx_exit(req->handler);
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act 
_athd_cmd_job(athd_cmd_req *req, char *cursor)
{
    athd_job *job = NULL;
    athd_conf *conf = athd_conf_current_get();
    if (cursor)
	cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
    ath_chompn(cursor, ATHD_MAX_CMD_RECV);
    if ((cursor) && (strlen(cursor) > 0))
      {
	  job = athd_job_for_name(conf->jobs, cursor);
	  if (job)
	    {
		req->handler->cmd_ctx->prompt =  
		  apr_psprintf(req->handler->cmd_ctx->pool, 
			       "(%s) ", job->name);
		req->handler->cmd_ctx->type = ATHD_CMD_JOB;
		req->handler->cmd_ctx->data = (void *)job;
	    }
	  else
	      CLIENT(ATHD_ERR_NOJOB);
      }
    else 
	_athd_cmd_show_all_jobs(req, cursor);
    if (!job)
	_athd_cmd_ctx_exit(req->handler);
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_farm(athd_cmd_req *req, char *cursor)
{
    if ((!req->handler->cmd_ctx->prev)
	|| (req->handler->cmd_ctx->prev->type != ATHD_CMD_ENGINE))
      {
	  CLIENT(ATHD_ERR_ENGCTX);
	  _athd_cmd_ctx_exit(req->handler);
      }
    else
      {
	  athd_engine *engine = (athd_engine *)
	    req->handler->cmd_ctx->prev->data;
	  ASSERT(engine);
	  _athd_cmd_update_farm_map(req, engine);
	  _athd_cmd_update_phys_map(req, engine);
	  if (cursor)
	      cursor += ath_skip_ws(cursor, ATHD_MAX_CMD_RECV);
	  ath_chompn(cursor, ATHD_MAX_CMD_RECV);
	  if ((cursor) && (strlen(cursor) > 0))
	    {
		ath_farm *farm = athd_engine_farm_map_get(engine, cursor);
		if (farm)
		  {
		      req->handler->cmd_ctx->data = farm;
		      req->handler->cmd_ctx->type = ATHD_CMD_FARM;
		      req->handler->cmd_ctx->prompt = 
			apr_psprintf(req->pool, "(%s) ",
				     farm->name);
		  }
		else 
		  {
		      _athd_cmd_ctx_exit(req->handler);
		      CLIENT(ATHD_ERR_NOFARM);
		  }
	    }
	  else
	    {
		ath_farm *farm;
		int i;
		for (i = 0; engine->clone_map_farm[i] != NULL; i++)
		  {
		      char *strfarm;
		      farm = engine->clone_map_farm[i];
		      strfarm = 
			apr_psprintf(req->pool, 
			       "%s\n  idx: %d on: %s\n", 
			       farm->name, farm->idx,
			       farm->on ? "yes" : "no");
		      CLIENT(strfarm);
		  }
		_athd_cmd_ctx_exit(req->handler);
	    }
      }
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_phys(athd_cmd_req *req, char *cursor)
{
    if ((!req->handler->cmd_ctx)
	|| (req->handler->cmd_ctx->type != ATHD_CMD_ENGINE))
	CLIENT(ATHD_ERR_ENGCTX);
    else
      {
	  athd_engine *engine = (athd_engine *)req->handler->cmd_ctx->data;
	  ASSERT(engine);
      }
    _athd_cmd_ctx_exit(req->handler);
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act 
_athd_cmd_context(athd_cmd_req *req, char *cursor)
{
    athd_cmd_ctx *ctx;
    
    ctx = req->handler->cmd_ctx;
    while (ctx)
      {
	  char *strout = 
	    apr_psprintf(req->pool,
			 "Cmd: %s [%d]\n Data ptr: [%d]\n Prev ptr: [%d]\n", 
			 ctx->cmd->name, ctx, ctx->data, ctx->prev);
	  CLIENT(strout);
	  ctx = ctx->prev;
      }
    _athd_cmd_ctx_exit(req->handler);
    athd_cmd_io_send_flush(req);
    return ATHD_ACT_CONTINUE;
}

/** cmd supporting fuctions **/

static athd_cmd_act
_athd_cmd_show_all_engines(athd_cmd_req *req, char *cursor)
{
    athd_conf *conf = athd_conf_current_get();
    ath_node *listctx;
    ath_list_rewind_r(conf->engines, &listctx);
    while (ath_list_hasnext_r(conf->engines, &listctx))
      {
	  athd_engine *eng = (athd_engine *)
	    ath_list_getnext_r(conf->engines, &listctx);
	  char *engstr = 
	    athd_engine_to_str(req->pool, eng);
	  CLIENT(engstr);
      }
    return ATHD_ACT_CONTINUE;
}

static athd_cmd_act
_athd_cmd_show_all_jobs(athd_cmd_req *req, char *cursor)
{
    athd_conf *conf = athd_conf_current_get();
    ath_node *listctx;
    ath_list_rewind_r(conf->jobs, &listctx);
    while (ath_list_hasnext_r(conf->jobs, &listctx))
      {
	  athd_job *job = (athd_job *)
	    ath_list_getnext_r(conf->jobs, &listctx);
	  char *jobstr = 
	    athd_job_to_str(req->pool, job);
	  CLIENT(jobstr);
      }
    return ATHD_ACT_CONTINUE;
}

static apr_status_t
_athd_cmd_update_phys_map(athd_cmd_req *req, athd_engine *engine)
{
    athd_request *areq;
    apr_status_t rv = APR_SUCCESS;
    apr_time_t now = apr_time_now();
    ASSERT(engine);

    /* just return if not expire */
    if (engine->clone_map_phys_mtime > (now - engine->clone_map_expire))
	return rv;

    areq = athd_req_create(req->pool, engine, ATHD_REQ_SERIAL_PHYS,
			   (const void *)ATHD_PHYS_ALL);
    athd_engine_push_req(areq);
    athd_req_response_wait(areq);
    if (areq->response_txt)
      {
	  char *serial, *inter;
	  char *sep = "\n";
	  serial = apr_strtok(areq->response_txt, sep, &inter);
	  while (serial)
	    {
		ath_phys *phys = ath_phys_clone_from_str(req->pool, serial);
		if (phys)
		    athd_engine_phys_map_set(engine, phys);
		else
		    CLIENT(ATHD_ERR_BADPHYS);
		serial = apr_strtok(NULL, sep, &inter);
	    }
	  /* assumes a complete update for now */
	  engine->clone_map_phys_mtime = now;
       }
    else
	rv = APR_EGENERAL;
    return rv;
}

static apr_status_t
_athd_cmd_update_farm_map(athd_cmd_req *req, athd_engine *engine)
{
    athd_request *areq;
    apr_status_t rv = APR_SUCCESS;
    apr_time_t now = apr_time_now();
    ASSERT(engine);

    /* just return if not expire */
    if (engine->clone_map_farm_mtime > (now - engine->clone_map_expire))
	return rv;

    areq = athd_req_create(req->pool, engine, ATHD_REQ_SERIAL_FARM,
			   (const void *)ATHD_FARM_ALL);
    athd_engine_push_req(areq);
    athd_req_response_wait(areq);
    if (areq->response_txt)
      {
	  char *serial, *inter;
	  char *sep = "\n";
	  serial = apr_strtok(areq->response_txt, sep, &inter);
	  while (serial)
	    {
		ath_farm *farm = ath_farm_clone_from_str(req->pool, serial);
		if (farm)
		    athd_engine_farm_map_set(engine, farm);
		else
		    CLIENT(ATHD_ERR_BADFARM);
		serial = apr_strtok(NULL, sep, &inter);
	    }
	  /* assumes a complete update for now */
	  engine->clone_map_farm_mtime = now;
      }
    else
	rv = APR_EGENERAL;
    return rv;
}

/** utils **/

static const athd_cmd *
_athd_cmd_get(const char *str)
{
    const athd_cmd *cmd;
    int i;
    ASSERT(str);
    for (i = 0; (_athd_cmd_map[i].name != NULL); i++)
      {
	  cmd = &_athd_cmd_map[i];
	  ASSERT(cmd->name);
	  if (!strncasecmp(str, cmd->name, strlen(cmd->name)))
	      return cmd;
      }
    return NULL;
}

static void
_athd_cmd_ctx_enter(athd_cmd_handler *handler, const athd_cmd *cmd)
{
    athd_cmd_ctx *cmd_ctx;
    if (handler->cmd_ctx)
      {
	  cmd_ctx = _athd_cmd_ctx_alloc(handler->cmd_ctx->pool);
	  cmd_ctx->prev = handler->cmd_ctx;
	  handler->cmd_ctx = cmd_ctx;
      }
    else
      {
	  cmd_ctx = _athd_cmd_ctx_alloc(handler->pool);
	  handler->cmd_ctx = cmd_ctx;
      }
    ASSERT(cmd_ctx);
    cmd_ctx->cmd = cmd;
    DEBUGOUT("cmd_ctx->cmd->name: %s", cmd_ctx->cmd->name);
}

static void
_athd_cmd_ctx_exit(athd_cmd_handler *handler)
{
    athd_cmd_ctx *cmd_ctx;
    apr_pool_t *pool;
    ASSERT(handler->cmd_ctx);
    DEBUGOUT("cmd_ctx->cmd->name: %s", handler->cmd_ctx->cmd->name);
    if (handler->cmd_ctx->prev)
      {
	  cmd_ctx = handler->cmd_ctx;
	  handler->cmd_ctx = handler->cmd_ctx->prev;
      }
    else
      {
	  cmd_ctx = handler->cmd_ctx;
	  handler->cmd_ctx = NULL;
      }
    pool = cmd_ctx->pool;
    apr_pool_destroy(pool);
    cmd_ctx = NULL;
}

static athd_cmd_ctx *
_athd_cmd_ctx_alloc(apr_pool_t *pool)
{
    athd_cmd_ctx *cmd_ctx;
    apr_pool_t *subpool;
    apr_pool_create(&subpool, pool);
    cmd_ctx = apr_pcalloc(subpool, sizeof(athd_cmd_ctx));
    ASSERT(cmd_ctx);
    cmd_ctx->pool = subpool;
    cmd_ctx->prev = NULL;
    cmd_ctx->data = NULL;
    cmd_ctx->prompt = NULL;
    cmd_ctx->type = ATHD_CMD_NULL;
    return cmd_ctx;
}

